<cfcomponent name="cfgoogleCheckOut" displayname="CFGoogleCheckOut" hint="Create checkout carts using the Google CheckOut API">
<!--- 
*******************************************************************************
 Copyright (C) 2006 Scott Pinkston
  
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
  
      http://www.apache.org/licenses/LICENSE-2.0
  
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*******************************************************************************

Component: cfgooglecheckOut
Created By: Scott Pinkston (spinkston@ozarka.edu)
Creation Date: 6/29/2006
Description:


Requirements:
cf_hmac module

		
http://www.adobe.com/cfusion/exchange/index.cfm?view=sn131&extID=1003921
Tim McCarthy (tim@timmcc.com)

 --->
	<!--- Set the cart up --->
	<cfset variables.cart = structNew()>

	<cfset variables.iniFile = ExpandPath("./Checkout.ini.cfm")>

	<cfif GetProfileString(iniFile, "default","demosite")>	
			<cfset variables.g_url = "https://sandbox.google.com/cws/v2/Merchant/">
		<cfelse>
			<cfset variables.g_url = "https://checkout.google.com/cws/v2/Merchant/">
	</cfif>		

	<cfset variables.ContinueURL = GetProfileString(iniFile, "default","continueurl")/>
	<cfset variables.EditCartURL = GetProfileString(iniFile, "default","editcarturl")/>
	
	<!--- *RYAN* Simple tax handling (only charge tax if delivery to your state) --->
	<cfset variables.ChargeTax= GetProfileString(iniFile, "default","chargetax")/>
	<cfset variables.StateToChargeTax=GetProfileString(iniFile, "default","statetochargetax")/>
	<cfset variables.TaxRate=GetProfileString(iniFile, "default","taxrate")/>
	
	<!--- *RYAN* Simple flat rate shipping --->
	<cfset variables.ChargeShipping=GetProfileString(iniFile, "default","chargeshipping")/>
	<cfset variables.allowFreePickup=GetProfileString(iniFile, "default","allowfreepickup")/>

	<cffunction name="GetMerchantKey" output="false" returntype="string" description="returns the merchant Key for checkout">
		<cfreturn GetProfileString(variables.iniFile, "default","merchantkey")>
	</cffunction>

	<cffunction name="GetMerchantID" output="false" returntype="string" description="returns the merchantID">
		<cfreturn getProfileString(variables.iniFile, "default", "merchantid")>
	</cffunction>

	<cffunction name="GetHttpAUTH" output="false" returntype="string" description="returns the encoded AUTH key for basic user authentication">
		<cfset var strCredential = getMerchantID() & ":" & getMerchantKey()>
		<cfreturn toBase64(strCredential)>
	</cffunction>
	
	<cffunction name="init" access="public" description="Initialize the object">
		<cfset var str = "">
		<cfreturn str>	
	</cffunction>

	<cffunction name="CreateCartXML" output="false" returntype="string" description="Creates the xml for google checkout">
		<cfargument name="CustNumber" required="false" type="string" default="">	

		<cfset var CartXMLHeader = "">
		<cfset var CartXMLItems = "">
		<cfset var CartXMLfooter = "">
		<Cfset var CartXML = "">
		
		<cfset var CartItems = GetCartItems()>
		<!--- *RYAN*  ---><cfset var shippingOptions = GetShippingOptions()>

		<cfoutput>
		<cfsavecontent variable="cartXMLHeader">
		<?xml version="1.0" encoding="UTF-8"?>
		<checkout-shopping-cart xmlns="http://checkout.google.com/schema/2">
		  <shopping-cart>
		    <items>
		</cfsavecontent>

		<cfsavecontent variable="cartXMLItems">
		<cfloop query="cartItems">
	      <item>
        	<item-name>#itemName#</item-name>
	   	    <item-description>#itemDesc#</item-description>
        	<unit-price currency="USD">#itemPrice#</unit-price>
	        <quantity>#itemQuantity#</quantity>
	      </item>
		</cfloop>
		</cfsavecontent>
		
		<!--- *RYAN*  --->
		<cfsavecontent variable="cartXMLTax">
			<cfif variables.ChargeTax>
				<tax-tables merchant-calculated="true">
					<default-tax-table>
						<tax-rules>
							<default-tax-rule>
								<shipping-taxed>false</shipping-taxed>
								<rate>#variables.TaxRate#</rate>
								<tax-area>
									<us-state-area>
										<state>#variables.StateToChargeTax#</state>
									</us-state-area>
								</tax-area>
							</default-tax-rule>
						</tax-rules>
					</default-tax-table>
				</tax-tables>
			</cfif>
		</cfsavecontent>
		
		<!--- *RYAN*  --->
		<cfsavecontent variable="cartXMLShipping">
				<shipping-methods>
					<cfloop query="shippingOptions">
						 <flat-rate-shipping name="#shipMethod#">
						  <price currency="USD">#shipPrice#</price>
						  <shipping-restrictions>
							<allowed-areas>
							  <us-country-area country-area="CONTINENTAL_48"></us-country-area>
							</allowed-areas>
						  </shipping-restrictions>
						</flat-rate-shipping>
					</cfloop>
					<cfif variables.allowFreePickup>
						<pickup name="Pickup">
					  		<price currency="USD">0.00</price>
						</pickup>
					</cfif>
				 </shipping-methods>
		</cfsavecontent>

		<cfsavecontent variable="cartXMLFooter">
		    </items>
		 	<merchant-private-data>
					<merchant-note>#arguments.CustNumber#</merchant-note>
		 	</merchant-private-data> 
		  </shopping-cart>
		  <checkout-flow-support>
		    <merchant-checkout-flow-support>
			   	<edit-cart-url>#Variables.EditCartURL#</edit-cart-url>
				<continue-shopping-url>#Variables.ContinueURL#</continue-shopping-url>
				#variables.cartXMLTax#
				#variables.cartXMLShipping#
			</merchant-checkout-flow-support>
		  </checkout-flow-support>

		</checkout-shopping-cart>
		</cfsavecontent>	
	
	</cfoutput>
		<cfset cartXMLData = cartXMLheader & cartXMLItems & cartXMLFooter>
		<cfreturn cartXMLData>
		
	</cffunction>
	
	<!--- *RYAN*  --->
	<cffunction name="GetShippingOptions" returntype="query" output="false" description="Gets the available shipping methods and pricing">
		<cfset var shippingOptions = "">
		
		<Cfset var temp = "">
		
		<!--- Replace this with real data --->
		<cfset shippingOptions = QueryNew("shipMethod,shipPrice")>
		
		<cfset temp = queryaddrow(shippingOptions,2)>
			
		<cfset temp = QuerySetCell(shippingOptions, "shipMethod", "UPS Ground", 1)>
		<cfset temp = QuerySetCell(shippingOptions, "shipPrice", "4.95", 1)>

		<cfset temp = QuerySetCell(shippingOptions, "shipMethod", "UPS 2nd Day Air", 2)>
		<cfset temp = QuerySetCell(shippingOptions, "shipPrice", "8.95", 2)>

		<cfreturn shippingOptions>
		
	</cffunction>

	<cffunction name="EmptyCart" returntype="void" output="false" description="Empties the cart">
		<cfset structClear(variables.cart)>
	</cffunction>	

	<cffunction name="RemoveCartItem" returntype="void" output="false" description="Removes an item from the cart">
		<cfargument name="tmpRemoveItem" required="true">
		<cfset structDelete(variables.cart,arguments.tmpRemoveItem)>
	</cffunction>	

	<cffunction name="UpdateCartItem" returntype="void" output="false" description="Updates the Cart Quantity">
		<cfargument name="item" required="true">
		<cfargument name="Quantity" required="true">	
	
			<cfif arguments.quantity lt 1>
					<cfset RemoveCartItem(arguments.item)>
				<cfelse>
					<cfset structUpdate(cart[arguments.item],"quantity",arguments.quantity)>
			</cfif>
	
	</cffunction>

	<cffunction access="remote" name="AddCartItem" returntype="query" output="false" description="Gets the list of cart items">
		<cfargument name="item" required="true">
		<cfargument name="description" required="true">
		<cfargument name="cost" required="true">
		<cfargument name="quantity" required="true">
		<cfset var tmpCart = "">

		<cfif structkeyExists(variables.cart,arguments.item)>
			<!--- already in cart, update item --->
			<cfset structUpdate(cart[arguments.item],"quantity",cart[arguments.item].quantity + arguments.quantity)>
		<cfelse>
			<!--- add to cart structure --->
			<cfset cart[arguments.item] = structNew()>
				<cfset cart[arguments.item].description = arguments.description>	
				<cfset cart[arguments.item].cost = arguments.cost>	
				<cfset cart[arguments.item].quantity = arguments.quantity>	
		</cfif>	

		<cfreturn getCartItems()>
	</cffunction>
		
	<cffunction name="GetCartItems" returntype="query" output="false" description="Gets the list of cart items">
		<cfset var cartitems = QueryNew("itemName,itemDesc,itemPrice,itemquantity,total")>
		<Cfset var temp = "">

		<!--- Loop over the structure and set as a query ---->
		<cfloop collection="#cart#" item="key">
			<cfset queryaddrow(cartItems,1)>
			<cfset QuerySetCell(cartitems, "itemName", key)>
			<cfset QuerySetCell(cartitems, "itemDesc", cart[key].description)>
			<cfset QuerySetCell(cartitems, "itemPrice", cart[key].cost)>
			<cfset QuerySetCell(cartitems, "itemQuantity", cart[key].quantity)>
			<cfset QuerySetCell(cartitems, "total", cart[key].quantity * cart[key].cost)>
		</cfloop>

		<cfreturn cartitems>
	</cffunction>
		
	<cffunction name="HMAC_SHA1" output="false" description="Creates the HMAC_SHA1 encrypted cart & key" returntype="string">
		<cfargument name="cartXML" required="true" type="string">

		<!--- call the module cf_hmac --->	
		<cf_hmac key="#getMerchantKey()#" data="#Arguments.cartXML#" hash_function="sha1">
			
		<cfreturn toBase64(Hex2Bin(digest, "hex"))>
	</cffunction>
	
	<cffunction name="CreateForm" output="false" description="Creates the form & submit button" returntype="string">
		<cfargument name="CustNumber" required="false" type="string" default="">	
		<cfset var cartXML = CreateCartXML(custNumber=arguments.custNumber)>
		<cfset var signature = HMAC_SHA1(cartXML)>
		<cfset var g_form = "">
		
		<cfoutput>
		<cfsavecontent variable="g_form">
		<form action="#variables.g_url##GetMerchantID()#/checkout" method="post">
		  <input type="hidden" name="cart" value="#toBase64(cartXML)#">
		  <input type="hidden" name="signature" value="#signature#">
		  <input type="image" name="Google Checkout" alt="Fast checkout through Google" src="http://checkout.google.com/buttons/checkout.gif?merchant_id=#GetMerchantID()#&w=180&h=46&style=white&variant=text&loc=en_US" height="46" width="180">
		</form>
		</cfsavecontent>
		</cfoutput>
		
		<cfreturn g_form>
	</cffunction>	

	<cffunction name="Hex2Bin" returntype="any" hint="Converts a Hex string to binary">
		<cfargument name="inputString" type="string" required="true" hint="The hexadecimal string to be written.">

		<cfset var outStream = CreateObject("java",
			"java.io.ByteArrayOutputStream").init()>
		
		<cfset var inputLength = Len(arguments.inputString)>
		<cfset var outputString = "">
		<cfset var i = 0>
		<cfset var ch = "">

		<cfif inputLength mod 2 neq 0>
			<cfset arguments.inputString = "0" & inputString>
		</cfif>

		<cfloop from="1" to="#inputLength#" index="i" step="2">
			<cfset ch = Mid(inputString, i, 2)>
				<cfset outStream.write(javacast("int", InputBaseN(ch, 16)))>
		</cfloop>

		<cfset outStream.flush()>
		<cfset outStream.close()>

		<cfreturn outStream.toByteArray()>
	</cffunction>
		
</cfcomponent>	
